#include "minet_socket.h"
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <string>
#include <list>
using namespace std;

#define FILENAMESIZE 100
#define BUFSIZE 1024

typedef enum { NEW,
	       READING_HEADERS,
	       WRITING_RESPONSE,
	       READING_FILE,
	       WRITING_FILE,
	       CLOSED } states;

typedef struct connection_s connection;

struct connection_s {
  int sock;
  int fd;
  char filename[FILENAMESIZE + 1];
  char buf[BUFSIZE + 1];
  int bufLen;
  bool ok;
  long filelen;
  states state;

  int headers_read;
  int response_written;
  int file_read;
  int file_written;
  
  //connection * next;
};

void read_headers(connection * con);
void write_response(connection * con);
void read_file(connection * con);
void write_file(connection * con);
void checkRV(int , const char *, int sock = 0);
void setNonblock(int fd);
bool isClosed(connection *p) {
  return (CLOSED == p->state);
}

static int fdmax; //maximum minet file descriptor number
static fd_set masterRead; //master minet readfd list
static fd_set masterWrite;//master minet writefds

int main(int argc, char * argv[]) {
  int server_port = -1;
  int sock = -1;
  struct sockaddr_in addr; //server
  struct sockaddr_in caddr;//client
  const static int BACKLOG = 5;//number of connection can be waiting
  fd_set readfds; //set of minet file descriptors to read
  fd_set writefds; //set of minet file descriptors to write
  struct timeval tv;//time interval
  tv.tv_sec = 2;
  tv.tv_usec = 500000;
  FD_ZERO(&readfds);
  FD_ZERO(&writefds);
  FD_ZERO(&masterRead);
  FD_ZERO(&masterWrite);
  list<connection *> l1; //list of connections

  /* parse command line args */
  if (argc != 3) {
    fprintf(stderr, "usage: http_server3 k|u port\n");
    exit(-1);
  }
  
  server_port = atoi(argv[2]);
  
  if (server_port < 1500) {
    fprintf(stderr, "INVALID PORT NUMBER: %d; can't be < 1500\n", server_port);
    exit(-1);
  }
  
  /* init */
  if(toupper(argv[1][0]) == 'K') {
    minet_init(MINET_KERNEL);
  }
  else if(toupper(argv[1][0]) == 'U') {
    minet_init(MINET_USER);
  }
  else {
    fprintf(stderr, "usage: http_server1 k|u port\n");
    exit(-1);
  }

  /* make socket */
  checkRV(sock = minet_socket(SOCK_STREAM), "Unable to create socket\n");
  
  /* set server address*/
  bzero(&addr, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(server_port);
  //inet_pton(AF_INET,"",&addr.sin_addr);
    
  /* bind listening socket */
  checkRV(minet_bind(sock, &addr), "Unable to bind socket\n",sock);
  
  /* start listening */
  checkRV(minet_listen(sock, BACKLOG), "Unable to listen\n",sock);

  /* connection handling loop */
  FD_SET(sock, &masterRead);//add sock to master Read fds
  fdmax = sock; //keep track of max fd
  l1.clear();//clear the connection list

  while (1) {
    /* create read and write lists */
    readfds = masterRead;
    writefds = masterWrite;
    /* do a select */
    checkRV(minet_select(fdmax+1, &readfds, &writefds, NULL, &tv), "Error on select", sock);
    
    if(FD_ISSET(sock, &readfds)) {//check if we have new connection  
      int newfd = minet_accept(sock, &caddr);//accept new connection
      if(newfd == -1) {//accept fail
	fprintf(stderr, "Error on accept %d\n", sock);
      }
      else {//accept succesful
	//cout<<"-----------New Connection --------------"<<endl;
	FD_SET(newfd, &masterRead);//add new conn to master read
	if(newfd > fdmax) {//update fdmax if necessary
	  fdmax = newfd;
	}
	minet_set_nonblocking(newfd);//set socket to non-blocking
	//-------------lacking error checking--------------
	connection *newc = new connection;
	memset(newc, 0, sizeof(connection));//set all to 0
	newc->sock = newfd;
	newc->state = NEW;
	newc->fd = -1;
	l1.push_back(newc);//add new connection to list
      }
    }//END new accept
    /* process sockets that are ready */
    for(list<connection *>::iterator it = l1.begin(); it!=l1.end(); it++) {
      connection *p = (*it);
      switch (p->state) {
      case NEW:
      case READING_HEADERS:
	if(FD_ISSET(p->sock, &readfds)) {//read from socket
	  read_headers(p);
	}
	break;
      case READING_FILE://reading local files
	if(FD_ISSET(p->fd, &readfds)) {//check if is readable
	  read_file(p);
	}
	break;
      case WRITING_RESPONSE://writing to socket
	if(FD_ISSET(p->sock, &writefds)) {
	  write_response(p);
	}
	break;
      case WRITING_FILE://write to socket
	if(FD_ISSET(p->sock, &writefds)) {
	  write_file(p);
	}
	break;
      default:
	//do nothing
	break;
      }//END switch p->state
      if(CLOSED == p->state) {//socket closed
	FD_CLR(p->sock, &masterWrite);
	FD_CLR(p->sock, &masterRead);
	if(-1 != p->fd) {
	  FD_CLR(p->fd, &masterRead);
	}
	minet_close(p->sock);
      }
    }//END search all possible connection
    l1.remove_if(isClosed);//remove all closed connection in list
  }//END infinite while loop
}

void read_headers(connection * con) {
  con->state = READING_HEADERS;
  string strReq;
  string strFilename;
  /* first read loop -- get request and headers*/
  int size = 0;
  do {
    con->headers_read += size;
    con->buf[con->headers_read] = '\0';
    if(con->headers_read >= 4 && 
       0 == strcmp(con->buf+con->headers_read-4,"\r\n\r\n")) {
      break;
    }
    size = minet_read(con->sock, con->buf+con->headers_read,BUFSIZE-con->headers_read);
    if(size == 0) {//connection closed
      con->state = CLOSED;
      //cout<<"closed"<<endl;
      return;
    }
    else if(-1 == size && EAGAIN == errno) {//egain error 
      con->state = READING_HEADERS;
      //cout<<"again"<<endl;
      return;
    }
  }while(size > 0 && con->headers_read < BUFSIZE); 
  strReq = con->buf;
  //cout<<"Request: "<<strReq<<endl;

  /* parse request to get file name */
  /* Assumption: this is a GET request and filename contains no spaces*/
  strFilename = strReq.substr(4,strReq.find(" ", 4)-4);
  if(strFilename[0] == '\\' || strFilename[0] == '/') 
    strFilename = strFilename.substr(1); //get rid of leading '/' or '\'
  
  /* get file name and size, set to non-blocking */
  int fd = open(strFilename.data(), O_RDONLY);//open file
  if(fd<0) {
    //fprintf(stderr, "Error: %s open failed!\n",strFilename.data());
    con->ok = false;
  }
  else {//valid file
    con->ok = true;
    con->fd = fd;
    setNonblock(fd);//set file to non block
    struct stat filestat;
    fstat(fd, &filestat);
    con->filelen = filestat.st_size;//get size of file
    strcpy(con->filename, strFilename.data());//copy name of file
    if(con->fd > fdmax) 
      fdmax = con->fd;
  }
  write_response(con);
}

void write_response(connection * con) {
  con->state = WRITING_RESPONSE;//update state
  FD_CLR(con->sock, &masterRead);//no need to read
  FD_SET(con->sock, &masterWrite);//need to write
  static char * ok_response_f = "HTTP/1.0 200 OK\r\n"		\
    "Content-type: text/plain\r\n"			\
    "Content-length: %d \r\n\r\n";
  static char * notok_response = "HTTP/1.0 404 FILE NOT FOUND\r\n"	\
    "Content-type: text/html\r\n\r\n"				\
    "<html><body bgColor=black text=white>\n"			\
    "<h2>404 FILE NOT FOUND</h2>\n"				\
    "</body></html>\n";
  static const int notok_response_len = strlen(notok_response);
  /* send response */
  if(con->ok) {//file exist
    if(0 == con->bufLen) {//we haven't set this before
      sprintf(con->buf, ok_response_f, con->filelen);//copy header into buf
      con->bufLen = strlen(con->buf);
    }
    
    //sending response
    int size = 0;
    while(con->response_written < con->bufLen) {
      size = minet_write(con->sock, con->buf + con->response_written,
			 con->bufLen - con->response_written);
      //cout<<"size = "<<size<<endl;
      if(0 == size) {//connection closed
	con->state = CLOSED;
	return;
      }
      else if(-1 == size && errno == EAGAIN) {//try again later
	con->state = WRITING_RESPONSE;
	return;
      }
      con->response_written += size;
    }
    con->bufLen = 0;
    con->buf[0] = '\0';//clear buf
    read_file(con);
  }
  else {//file not exist
    //sending response
    int size = 0;
    while(con->response_written < notok_response_len) {
      size = minet_write(con->sock, notok_response + con->response_written, 
			 notok_response_len - con->response_written);
      if(0 == size) {//connection closed
	con->state = CLOSED;
	return;
      }
      else if(-1 == size && errno == EAGAIN) {//try again later
	con->state = WRITING_RESPONSE;
	return;
      }
      con->response_written += size;
    }
   con->state = CLOSED;
  }

}

void read_file(connection * con) {
  FD_CLR(con->sock, &masterWrite);//no need to write
  FD_SET(con->fd, &masterRead);//need to read
  con->state = READING_FILE;
  int size = 0;
  while(con->bufLen < BUFSIZE) {
    size = read(con->fd, con->buf+con->bufLen, BUFSIZE - con->bufLen);//read from file
    if(0 == size) {//end of file or blocked
      break;
    }  
    else if (-1 == size) {//read failed
      perror("Read file Error: ");
      break;
    }
    con->bufLen += size;
    con->buf[con->bufLen] = '\0';
    con->file_read += size;
  }
  //cout<<con->bufLen<<"   "<<con->buf<<endl;
  if(con->file_read == con->filelen) {//all file content read
    close(con->fd);//close file
    write_file(con);
  } 
  else if(con->bufLen == BUFSIZE) {//buf is full
    write_file(con);
  }
}

void write_file(connection * con) {//cout<<"write "<<con->file_written<<endl;
  con->state = WRITING_FILE;
  FD_CLR(con->fd, &masterRead);//no need to read from file
  FD_SET(con->sock, &masterWrite);//add sock to write fds

  int size;
  while(con->bufLen) {//while buffer is not empty
    size = minet_write(con->sock, con->buf + (con->file_written % BUFSIZE), con->bufLen);
    //cout<<size<<endl<<(con->buf+con->file_read % BUFSIZE)<<endl;
    if(size <= 0) {
      minet_perror("Minet write fail: ");
      break;
    } 
    con->bufLen -= size;
    con->file_written += size;
  } 
  if(con->file_written == con->filelen) {//all done!
    con->state = CLOSED;
  }
  else if(con->bufLen == 0) {//buf is empty but not file send
    read_file(con);//start read and write
  }

}

/*check return value*/
inline void checkRV(int rt, const char * err, int sock) {
  if(rt < 0) {
    fprintf(stderr, err);
    minet_perror("Reason: ");
    minet_deinit();
    if(sock) {
      minet_close(sock);
    }
    exit(-1);
  }
}

void setNonblock(int fd) {
  if(fd <0) 
    return;
  int flags;
  if(-1 == (flags = fcntl(fd, F_GETFL, 0))) {
    fprintf(stderr, "Error: fcntl(F_GETFL)\n");
    exit(-1);
  }
  if(fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
    fprintf(stderr, "Error: fcntl(F_GETFL, O_NONBLOCK)\n");
    exit(-1);
  }
}
